/*
 * Copyright (c) 2011, Kustaa Nyholm / SpareTimeLabs
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this list 
 * of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright notice, this 
 * list of conditions and the following disclaimer in the documentation and/or other
 * materials provided with the distribution.
 *  
 * Neither the name of the Kustaa Nyholm or SpareTimeLabs nor the names of its 
 * contributors may be used to endorse or promote products derived from this software 
 * without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

package jtermios.linux;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.IOException;

import java.lang.reflect.Method;
import java.nio.Buffer;

import java.nio.ByteBuffer;
import java.util.*;
import java.util.regex.Pattern;

import com.sun.jna.*;
import jtermios.*;

import jtermios.JTermios.JTermiosInterface;
import jtermios.linux.JTermiosImpl.Linux_C_lib.pollfd;
import jtermios.linux.JTermiosImpl.Linux_C_lib.serial_struct;

import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.NativeLongByReference;

import static jtermios.JTermios.*;
import static jtermios.JTermios.JTermiosLogging.log;

public class JTermiosImpl implements jtermios.JTermios.JTermiosInterface {
	private static String DEVICE_DIR_PATH = "/dev/";
	private static final boolean IS64B = NativeLong.SIZE == 8;
	static Linux_C_lib_DirectMapping m_ClibDM = new Linux_C_lib_DirectMapping();
	static Linux_C_lib m_Clib = m_ClibDM;

	private final static int TIOCGSERIAL = 0x0000541E;
	private final static int TIOCSSERIAL = 0x0000541F;

	private final static int ASYNC_SPD_MASK = 0x00001030;
	private final static int ASYNC_SPD_CUST = 0x00000030;

	private final static int[] m_BaudRates = { //
	50, 0000001, //
			75, 0000002, //
			110, 0000003, //
			134, 0000004, //
			150, 0000005, //
			200, 0000006, //
			300, 0000007, //
			600, 0000010, //
			1200, 0000011, //
			1800, 0000012, //
			2400, 0000013, //
			4800, 0000014, //
			9600, 0000015, //
			19200, 0000016, //
			38400, 0000017, //
			57600, 0010001, //
			115200, 0010002, //
			230400, 0010003, //
			460800, 0010004, //
			500000, 0010005, //
			576000, 0010006, //
			921600, 0010007, //
			1000000, 0010010, //
			1152000, 0010011, //
			1500000, 0010012, //
			2000000, 0010013, //
			2500000, 0010014, //
			3000000, 0010015, //
			3500000, 0010016, //
			4000000, 0010017 //
	};

	public static class Linux_C_lib_DirectMapping implements Linux_C_lib {
		native public long memcpy(int[] dst, short[] src, long n);

		native public int memcpy(int[] dst, short[] src, int n);

		native public int pipe(int[] fds);

		native public int tcdrain(int fd);

		native public void cfmakeraw(termios termios);

		native public int fcntl(int fd, int cmd, int arg);

		native public int ioctl(int fd, int cmd, int[] arg);

		native public int ioctl(int fd, int cmd, serial_struct arg);

		native public int open(String path, int flags);

		native public int close(int fd);

		native public int tcgetattr(int fd, termios termios);

		native public int tcsetattr(int fd, int cmd, termios termios);

		native public int cfsetispeed(termios termios, NativeLong i);

		native public int cfsetospeed(termios termios, NativeLong i);

		native public NativeLong cfgetispeed(termios termios);

		native public NativeLong cfgetospeed(termios termios);

		native public int write(int fd, byte[] buffer, int count);

		native public int read(int fd, byte[] buffer, int count);

		native public long write(int fd, byte[] buffer, long count);

		native public long read(int fd, byte[] buffer, long count);

		native public int select(int n, int[] read, int[] write, int[] error, timeval timeout);

		native public int poll(int[] fds, int nfds, int timeout);

		public int poll(pollfd[] fds, int nfds, int timeout) {
			throw new IllegalArgumentException();
		}

		native public int tcflush(int fd, int qs);

		native public void perror(String msg);

		native public int tcsendbreak(int fd, int duration);

		static {
			try {
				Native.register(NativeLibrary.getInstance("c", YJPFunctionMapper.OPTIONS));
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	public interface Linux_C_lib extends com.sun.jna.Library {
		public long memcpy(int[] dst, short[] src, long n);

		public int memcpy(int[] dst, short[] src, int n);

		public int pipe(int[] fds);

		public int tcdrain(int fd);

		public void cfmakeraw(termios termios);

		public int fcntl(int fd, int cmd, int arg);

		public int ioctl(int fd, int cmd, int[] arg);

		public int ioctl(int fd, int cmd, serial_struct arg);

		public int open(String path, int flags);

		public int close(int fd);

		public int tcgetattr(int fd, termios termios);

		public int tcsetattr(int fd, int cmd, termios termios);

		public int cfsetispeed(termios termios, NativeLong i);

		public int cfsetospeed(termios termios, NativeLong i);

		public NativeLong cfgetispeed(termios termios);

		public NativeLong cfgetospeed(termios termios);

		public int write(int fd, byte[] buffer, int count);

		public int read(int fd, byte[] buffer, int count);

		public long write(int fd, byte[] buffer, long count);

		public long read(int fd, byte[] buffer, long count);

		public int select(int n, int[] read, int[] write, int[] error, timeval timeout);

		public int poll(pollfd[] fds, int nfds, int timeout);

		public int poll(int[] fds, int nfds, int timeout);

		public int tcflush(int fd, int qs);

		public void perror(String msg);

		public int tcsendbreak(int fd, int duration);

		static public class timeval extends Structure {
			public NativeLong tv_sec;
			public NativeLong tv_usec;

			@Override
			protected List getFieldOrder() {
				return Arrays.asList(//
						"tv_sec",//
						"tv_usec"//
				);
			}

			public timeval(jtermios.TimeVal timeout) {
				tv_sec = new NativeLong(timeout.tv_sec);
				tv_usec = new NativeLong(timeout.tv_usec);
			}
		}

		static public class pollfd extends Structure {
			public int fd;
			public short events;
			public short revents;

			@Override
			protected List getFieldOrder() {
				return Arrays.asList(//
						"fd",//
						"events",//
						"revents"//
				);
			}

			public pollfd(Pollfd pfd) {
				fd = pfd.fd;
				events = pfd.events;
				revents = pfd.revents;
			}
		}

		public static class serial_struct extends Structure {
			public int type;
			public int line;
			public int port;
			public int irq;
			public int flags;
			public int xmit_fifo_size;
			public int custom_divisor;
			public int baud_base;
			public short close_delay;
			public short io_type;
			//public char io_type;
			//public char reserved_char;
			public int hub6;
			public short closing_wait;
			public short closing_wait2;
			public Pointer iomem_base;
			public short iomem_reg_shift;
			public int port_high;
			public NativeLong iomap_base;

			@Override
			protected List getFieldOrder() {
				return Arrays.asList(//
						"type",//
						"line",//
						"port",//
						"irq",//
						"flags",//
						"xmit_fifo_size",//
						"custom_divisor",//
						"baud_base",//
						"close_delay",//
						"io_type",//
						//public char io_type;
						//public char reserved_char;
						"hub6",//
						"closing_wait",//
						"closing_wait2",//
						"iomem_base",//
						"iomem_reg_shift",//
						"port_high",//
						"iomap_base"//
				);
			}
		};

		static public class termios extends Structure {
			public int c_iflag;
			public int c_oflag;
			public int c_cflag;
			public int c_lflag;
			public byte c_line;
			public byte[] c_cc = new byte[32];
			public int c_ispeed;
			public int c_ospeed;

			@Override
			protected List getFieldOrder() {
				return Arrays.asList(//
						"c_iflag",//
						"c_oflag",//
						"c_cflag",//
						"c_lflag",//
						"c_line",//
						"c_cc",//
						"c_ispeed",//
						"c_ospeed"//
				);
			}

			public termios() {
			}

			public termios(jtermios.Termios t) {
				c_iflag = t.c_iflag;
				c_oflag = t.c_oflag;
				c_cflag = t.c_cflag;
				c_lflag = t.c_lflag;
				System.arraycopy(t.c_cc, 0, c_cc, 0, t.c_cc.length);
				c_ispeed = t.c_ispeed;
				c_ospeed = t.c_ospeed;
			}

			public void update(jtermios.Termios t) {
				t.c_iflag = c_iflag;
				t.c_oflag = c_oflag;
				t.c_cflag = c_cflag;
				t.c_lflag = c_lflag;
				System.arraycopy(c_cc, 0, t.c_cc, 0, t.c_cc.length);
				t.c_ispeed = c_ispeed;
				t.c_ospeed = c_ospeed;
			}
		}

	}

	static private class FDSetImpl extends FDSet {
		static final int FD_SET_SIZE = 1024;
		static final int NFBBITS = 32;
		int[] bits = new int[(FD_SET_SIZE + NFBBITS - 1) / NFBBITS];

		public String toString() {
			return String.format("%08X%08X", bits[0], bits[1]);
		}
	}

	public JTermiosImpl() {
		log = log && log(1, "instantiating %s\n", getClass().getCanonicalName());

		//linux/serial.h stuff
		FIONREAD = 0x541B; // Looked up manually
		//fcntl.h stuff
		O_RDWR = 0x00000002;
		O_NONBLOCK = 0x00000800;
		O_NOCTTY = 0x00000100;
		O_NDELAY = 0x00000800;
		F_GETFL = 0x00000003;
		F_SETFL = 0x00000004;
		//errno.h stuff
		EAGAIN = 11;
		EACCES = 13;
		EEXIST = 17;
		EINTR = 4;
		EINVAL = 22;
		EIO = 5;
		EISDIR = 21;
		ELOOP = 40;
		EMFILE = 24;
		ENAMETOOLONG = 36;
		ENFILE = 23;
		ENOENT = 2;
		ENOSR = 63;
		ENOSPC = 28;
		ENOTDIR = 20;
		ENXIO = 6;
		EOVERFLOW = 75;
		EROFS = 30;
		ENOTSUP = 95;
		//termios.h stuff
		TIOCM_RNG = 0x00000080;
		TIOCM_CAR = 0x00000040;
		IGNBRK = 0x00000001;
		BRKINT = 0x00000002;
		IGNPAR = 0x00000004;
		PARMRK = 0x00000008;
		INLCR = 0x00000040;
		IGNCR = 0x00000080;
		ICRNL = 0x00000100;
		ECHONL = 0x00000040;
		IEXTEN = 0x00008000;
		CLOCAL = 0x00000800;
		OPOST = 0x00000001;
		VSTART = 0x00000008;
		TCSANOW = 0x00000000;
		VSTOP = 0x00000009;
		VMIN = 0x00000006;
		VTIME = 0x00000005;
		VEOF = 0x00000004;
		TIOCMGET = 0x00005415;
		TIOCM_CTS = 0x00000020;
		TIOCM_DSR = 0x00000100;
		TIOCM_RI = 0x00000080;
		TIOCM_CD = 0x00000040;
		TIOCM_DTR = 0x00000002;
		TIOCM_RTS = 0x00000004;
		ICANON = 0x00000002;
		ECHO = 0x00000008;
		ECHOE = 0x00000010;
		ISIG = 0x00000001;
		TIOCMSET = 0x00005418;
		IXON = 0x00000400;
		IXOFF = 0x00001000;
		IXANY = 0x00000800;
		CRTSCTS = 0x80000000;
		TCSADRAIN = 0x00000001;
		INPCK = 0x00000010;
		ISTRIP = 0x00000020;
		CSIZE = 0x00000030;
		TCIFLUSH = 0x00000000;
		TCOFLUSH = 0x00000001;
		TCIOFLUSH = 0x00000002;
		CS5 = 0x00000000;
		CS6 = 0x00000010;
		CS7 = 0x00000020;
		CS8 = 0x00000030;
		CSTOPB = 0x00000040;
		CREAD = 0x00000080;
		PARENB = 0x00000100;
		PARODD = 0x00000200;
		B0 = 0;
		B50 = 1;
		B75 = 2;
		B110 = 3;
		B134 = 4;
		B150 = 5;
		B200 = 6;
		B300 = 7;
		B600 = 8;
		B1200 = 9;
		B1800 = 10;
		B2400 = 11;
		B4800 = 12;
		B9600 = 13;
		B19200 = 14;
		B38400 = 15;
		B57600 = 4097;
		B115200 = 4098;
		B230400 = 4099;
		//poll.h stuff
		POLLIN = 0x0001;
		POLLPRI = 0x0002;
		POLLOUT = 0x0004;
		POLLERR = 0x0008;
		POLLNVAL = 0x0020;

		// these depend on the endianness off the machine
		POLLIN_IN = pollMask(0, POLLIN);
		POLLOUT_IN = pollMask(0, POLLOUT);

		POLLIN_OUT = pollMask(1, POLLIN);
		POLLOUT_OUT = pollMask(1, POLLOUT);
		POLLNVAL_OUT = pollMask(1, POLLNVAL);
	}

	public static int pollMask(int i, short n) {
		short[] s = new short[2];
		int[] d = new int[1];
		s[i] = n;
		// the native call depends on weather this is 32 or 64 bit arc
		if (IS64B)
			m_ClibDM.memcpy(d, s, (long) 4);
		else
			m_ClibDM.memcpy(d, s, (int) 4);
		return d[0];
	}

	public int errno() {
		return Native.getLastError();
	}

	public void cfmakeraw(Termios termios) {
		Linux_C_lib.termios t = new Linux_C_lib.termios(termios);
		m_Clib.cfmakeraw(t);
		t.update(termios);
	}

	public int fcntl(int fd, int cmd, int arg) {
		return m_Clib.fcntl(fd, cmd, arg);
	}

	public int tcdrain(int fd) {
		return m_Clib.tcdrain(fd);
	}

	public int cfgetispeed(Termios termios) {
		return m_Clib.cfgetispeed(new Linux_C_lib.termios(termios)).intValue();
	}

	public int cfgetospeed(Termios termios) {
		return m_Clib.cfgetospeed(new Linux_C_lib.termios(termios)).intValue();
	}

	public int cfsetispeed(Termios termios, int speed) {
		Linux_C_lib.termios t = new Linux_C_lib.termios(termios);
		int ret = m_Clib.cfsetispeed(t, new NativeLong(speed));
		t.update(termios);
		return ret;
	}

	public int cfsetospeed(Termios termios, int speed) {
		Linux_C_lib.termios t = new Linux_C_lib.termios(termios);
		int ret = m_Clib.cfsetospeed(t, new NativeLong(speed));
		t.update(termios);
		return ret;
	}

	public int open(String s, int t) {
		if (s != null && !s.startsWith("/"))
			s = DEVICE_DIR_PATH + s;
		return m_Clib.open(s, t);
	}

	public int read(int fd, byte[] buffer, int len) {
		if (IS64B)
			return (int) m_Clib.read(fd, buffer, (long) len);
		else
			return m_Clib.read(fd, buffer, len);
	}

	public int write(int fd, byte[] buffer, int len) {
		if (IS64B)
			return (int) m_Clib.write(fd, buffer, (long) len);
		else
			return m_Clib.write(fd, buffer, len);
	}

	public int close(int fd) {
		return m_Clib.close(fd);
	}

	public int tcflush(int fd, int b) {
		return m_Clib.tcflush(fd, b);
	}

	public int tcgetattr(int fd, Termios termios) {
		Linux_C_lib.termios t = new Linux_C_lib.termios();
		int ret = m_Clib.tcgetattr(fd, t);
		t.update(termios);
		return ret;
	}

	public void perror(String msg) {
		m_Clib.perror(msg);
	}

	public int tcsendbreak(int fd, int duration) {
		// If duration is not zero, it sends zero-valued bits for duration*N seconds,
		// where N is at least 0.25, and not more than 0.5.
		return m_Clib.tcsendbreak(fd, duration / 250);
	}

	public int tcsetattr(int fd, int cmd, Termios termios) {
		return m_Clib.tcsetattr(fd, cmd, new Linux_C_lib.termios(termios));
	}

	public void FD_CLR(int fd, FDSet set) {
		if (set == null)
			return;
		FDSetImpl p = (FDSetImpl) set;
		p.bits[fd / FDSetImpl.NFBBITS] &= ~(1 << (fd % FDSetImpl.NFBBITS));
	}

	public boolean FD_ISSET(int fd, FDSet set) {
		if (set == null)
			return false;
		FDSetImpl p = (FDSetImpl) set;
		return (p.bits[fd / FDSetImpl.NFBBITS] & (1 << (fd % FDSetImpl.NFBBITS))) != 0;
	}

	public void FD_SET(int fd, FDSet set) {
		if (set == null)
			return;
		FDSetImpl p = (FDSetImpl) set;
		p.bits[fd / FDSetImpl.NFBBITS] |= 1 << (fd % FDSetImpl.NFBBITS);
	}

	public void FD_ZERO(FDSet set) {
		if (set == null)
			return;
		FDSetImpl p = (FDSetImpl) set;
		java.util.Arrays.fill(p.bits, 0);
	}

	public int select(int nfds, FDSet rfds, FDSet wfds, FDSet efds, TimeVal timeout) {
		Linux_C_lib.timeval tout = null;
		if (timeout != null)
			tout = new Linux_C_lib.timeval(timeout);

		int[] r = rfds != null ? ((FDSetImpl) rfds).bits : null;
		int[] w = wfds != null ? ((FDSetImpl) wfds).bits : null;
		int[] e = efds != null ? ((FDSetImpl) efds).bits : null;
		return m_Clib.select(nfds, r, w, e, tout);
	}

	public int poll(Pollfd fds[], int nfds, int timeout) {
		pollfd[] pfds = new pollfd[fds.length];
		for (int i = 0; i < nfds; i++)
			pfds[i] = new pollfd(fds[i]);
		int ret = m_Clib.poll(pfds, nfds, timeout);
		for (int i = 0; i < nfds; i++)
			fds[i].revents = pfds[i].revents;
		return ret;
	}

	public int poll(int fds[], int nfds, int timeout) {
		return m_Clib.poll(fds, nfds, timeout);
	}

	public FDSet newFDSet() {
		return new FDSetImpl();
	}

	public int ioctl(int fd, int cmd, int[] data) {
		return m_Clib.ioctl(fd, cmd, data);
	}

	// This ioctl is Linux specific, so keep it private for now
	private int ioctl(int fd, int cmd, serial_struct data) {
		// Do the logging here as this does not go through the JTermios which normally does the logging
		log = log && log(5, "> ioctl(%d,%d,%s)\n", fd, cmd, data);
		int ret = m_Clib.ioctl(fd, cmd, data);
		log = log && log(3, "< tcsetattr(%d,%d,%s) => %d\n", fd, cmd, data, ret);
		return ret;
	}

	public String getPortNamePattern() {
		// First we have to determine which serial drivers exist and which
		// prefixes they use
		final List<String> prefixes = new ArrayList<String>();

		try {
			BufferedReader drivers = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/tty/drivers"), "US-ASCII"));
			String line;
			while ((line = drivers.readLine()) != null) {
				// /proc/tty/drivers contains the prefix in the second column
				// and "serial" in the fifth

				String[] parts = line.split(" +");
				if (parts.length != 5) {
					continue;
				}

				if (!"serial".equals(parts[4])) {
					continue;
				}

				// Sanity check the prefix
				if (!parts[1].startsWith("/dev/")) {
					continue;
				}

				prefixes.add(parts[1].substring(5));
			}
			drivers.close();
		} catch (IOException e) {
			log = log && log(1, "failed to read /proc/tty/drivers\n");

			prefixes.add("ttyS");
			prefixes.add("ttyUSB");
			prefixes.add("ttyACM");
		}

		// Now build the pattern from the known prefixes

		StringBuilder pattern = new StringBuilder();

		pattern.append('^');

		boolean first = true;
		for (String prefix : prefixes) {
			if (first) {
				first = false;
			} else {
				pattern.append('|');
			}

			pattern.append("(");
			pattern.append(prefix);
			pattern.append(".+)");
		}

		return pattern.toString();
	}

	public List<String> getPortList() {
		File dir = new File(DEVICE_DIR_PATH);
		if (!dir.isDirectory()) {
			log = log && log(1, "device directory %s does not exist\n", DEVICE_DIR_PATH);
			return null;
		}
		String[] devs = dir.list();
		LinkedList<String> list = new LinkedList<String>();

		Pattern p = JTermios.getPortNamePattern(this);
		if (devs != null) {
			for (int i = 0; i < devs.length; i++) {
				String s = devs[i];
				if (p.matcher(s).matches())
					list.add(s);
			}
		}
		return list;
	}

	public void shutDown() {

	}

	public int setspeed(int fd, Termios termios, int speed) {
		int c = speed;
		int r;
		for (int i = 0; i < m_BaudRates.length; i += 2) {
			if (m_BaudRates[i] == speed) {

				// found the baudrate from the table

				// just in case custom divisor was in use, try to turn it off first
				serial_struct ss = new serial_struct();

				r = ioctl(fd, TIOCGSERIAL, ss);
				if (r == 0) {
					ss.flags &= ~ASYNC_SPD_MASK;
					r = ioctl(fd, TIOCSSERIAL, ss);
				}

				// now set the speed with the constant from the table
				c = m_BaudRates[i + 1];
				if ((r = JTermios.cfsetispeed(termios, c)) != 0)
					return r;
				if ((r = JTermios.cfsetospeed(termios, c)) != 0)
					return r;
				if ((r = JTermios.tcsetattr(fd, TCSANOW, termios)) != 0)
					return r;

				return 0;
			}
		}

		// baudrate not defined in the table, try custom divisor approach

		// configure port to use custom speed instead of 38400
		serial_struct ss = new serial_struct();
		if ((r = ioctl(fd, TIOCGSERIAL, ss)) != 0)
			return r;
		ss.flags = (ss.flags & ~ASYNC_SPD_MASK) | ASYNC_SPD_CUST;

		if (speed == 0) {
			log = log && log(1, "unable to set custom baudrate %d \n", speed);
			return -1;
		}

		ss.custom_divisor = (ss.baud_base + (speed / 2)) / speed;

		if (ss.custom_divisor == 0) {
			log = log && log(1, "unable to set custom baudrate %d (possible division by zero)\n", speed);
			return -1;
		}

		int closestSpeed = ss.baud_base / ss.custom_divisor;

		if (closestSpeed < speed * 98 / 100 || closestSpeed > speed * 102 / 100) {
			log = log && log(1, "best available baudrate %d not close enough to requested %d \n", closestSpeed, speed);
			return -1;
		}

		if ((r = ioctl(fd, TIOCSSERIAL, ss)) != 0)
			return r;

		if ((r = JTermios.cfsetispeed(termios, B38400)) != 0)
			return r;
		if ((r = JTermios.cfsetospeed(termios, B38400)) != 0)
			return r;
		if ((r = JTermios.tcsetattr(fd, TCSANOW, termios)) != 0)
			return r;
		return 0;
	}

	public int pipe(int[] fds) {
		return m_Clib.pipe(fds);
	}
}
